#include "JefeDeEstacion.h"


JefeDeEstacion::JefeDeEstacion(const string& nombreProceso, bool log_activo,
		const int cantidad_empleados) :
		Proceso(nombreProceso, log_activo), fifo(
				Constantes::ARCHIVO_FIFO_PRODUCTOR_CONSUMIDOR_DE_CLIENTES), cantidad_empleados_(
				cantidad_empleados), indentificadores_empleados_(cantidad_empleados) {
	prepararRecursos();
	for (int j = 0; j < cantidad_empleados; j++) {
		agregarEmpleado(j);
	}
}

void JefeDeEstacion::prepararRecursos() {
	memorias_jefe_empleado_.reserve(cantidad_empleados_);
	empleados_ocupados_.reserve(cantidad_empleados_);
	flags_estado_.reserve(cantidad_empleados_);
	flags_memorias_.reserve(cantidad_empleados_);
}

void JefeDeEstacion::agregarEmpleado(const int j) {
	memorias_jefe_empleado_.push_back(
			MemoriaCompartida<Cliente>(
					Constantes::ARCHIVO_CLAVE_JEFE_EMPLEADO, j));
	MemoriaCompartida<bool> memoria(
			MemoriaCompartida<bool>(
					Constantes::ARCHIVO_CLAVE_ESTADO_JEFE_EMPLEADO, j));
	memoria.escribir(false);
	empleados_ocupados_.push_back(memoria);
	flags_memorias_.push_back(
			Semaforo(Constantes::ARCHIVO_CLAVE_SEMAFORO_JEFE_EMPLEADO,
					cantidad_empleados_ + j, 0));
	flags_estado_.push_back(
			Semaforo(Constantes::ARCHIVO_CLAVE_SEMAFORO_ESTADO_EMPLEADO, j,
					1));
}

JefeDeEstacion::~JefeDeEstacion() {
}

void JefeDeEstacion::procesarCliente(Cliente& cliente) {
	bool procesado = false;
	unsigned int i = 0;
	while (!procesado && (i < flags_estado_.size())) {
		if (flags_estado_[i].p() < 0) {
			getLogger().error(
					"Error en Estado de Empleado en operacion 'p'");
		}
		// Si el empleado no esta ocupado intenta asignarle cliente
		if (!empleados_ocupados_[i].leer()) {
			getLogger().info("Se le asignara el empleado Nº:"+Utilidades::int_to_string(i + 1)
			+" al cliente Nº "+ Utilidades::int_to_string(cliente.getId()));
			procesado = true;
			if (flags_estado_[i].v() < 0) {
				getLogger().error(
						"Error en Estado de Empleado en operacion 'v'.");
			}
			try {
				bool res1 = false; bool res2 = false;
				bool res3 = false; bool res4 = false;
				memorias_jefe_empleado_[i].escribir(cliente);
				res1 = flags_estado_[i].p();
				empleados_ocupados_[i].escribir(true);
				res3 = flags_estado_[i].v();
				res4 = flags_memorias_[i].v();
				if(res1 < 0 || res2 < 0 || res3 < 0 || res4 < 0)
					getLogger().error("Error de sincronización con Empleado");
			} catch (std::exception& e) {
				getLogger().error("Error al intentar pasar un cliente");
			}
		//Si esta ocupado libera el semaforo y no hace nada
		} else {
			if (flags_estado_[i].v() < 0) {
				getLogger().error(
						"Error en Estado de Empleado en operacion 'v'.");
			}
		}
		++i;
	}
	// Si el cliente no fue procesado es porque no habia empleado, se va
	if(!procesado) {
		getLogger().info("No hay empleado disponible, el cliente Nº "+
				Utilidades::int_to_string(cliente.getId())+" se va de la estacion.");
	}
}

void JefeDeEstacion::consumirCliente(const long int ultimo_cliente) {
	char buffer;
	fifo.leer(&buffer, 1);
	Cliente cliente;
	cliente.setId(ultimo_cliente);
	cliente.setCombustible(
			GeneradorRandom::obtenerNumero(Constantes::MINIMO_NAFTA_CLIENTES,
					Constantes::MAXIMO_NAFTA_CLIENTES));
	// Tiempo entre que consume cliente y procede a asignarle empleado
	// Es lo que se llama hablando con el cliente en el diagrama de estados del jefe
	sleep(
			GeneradorRandom::obtenerNumero(
					Constantes::MINIMO_TIEMPO_ATENCION_JEFE,
					Constantes::MAXIMO_TIEMPO_ATENCION_JEFE));
	getLogger().info("Ya hable con el cliente N° " + Utilidades::int_to_string(cliente.getId())
	+ " se le intentara encontrar un empleado.");
	this->procesarCliente(cliente);
}

void JefeDeEstacion::run() {
	int codigoSalida = 0;

	SIGINT_Handler sigint_handler;
	// Registrar el sigint_handler
	SignalHandler::getInstance()->registrarHandler(SIGINT, &sigint_handler);
	fifo.abrir();
	long int clientes = 1;

	// Consume clientes indefinidamente
	while (sigint_handler.getGracefulQuit() == 0) {
		try {
			consumirCliente(clientes);
			clientes++;
			getLogger().info("Consumi un cliente de los que estaban esperando");
		} catch (const CantReadException& e) {
			/* Si no puede leer del fifo, termina de consumir
			 * Aca va a entrar si se cierra el productor antes que el jefe
			 * Es algo normal que pase al finalizar el programa. */
			getLogger().error("No se pudo leer del fifo");
			codigoSalida = 1;
			break;
		}
	}
	// se recibio la senial SIGINT, el proceso termina
	if (codigoSalida == 0) {
		getLogger().warn("Se recibio la senial SIGINT, el proceso se termina.");
	}

	SignalHandler::destruir();
}

void JefeDeEstacion::eliminar() {
	fifo.cerrar();
	fifo.eliminar();
	for (int i = 0; i < cantidad_empleados_; ++i) {
		std::stringstream ss;
		string numero_empleados_str;
		numero_empleados_str.append("Se procedera a liberar el Empleado ");
		numero_empleados_str.append(Utilidades::int_to_string(i+1));
		getLogger().warn(numero_empleados_str.c_str());
		eliminarEmpleado(i);
	}
}

void JefeDeEstacion::eliminarEmpleado(const int nEmpleado) {
	this->flags_estado_[nEmpleado].eliminar();
	this->flags_memorias_[nEmpleado].eliminar();
	this->memorias_jefe_empleado_[nEmpleado].liberar();
	this->empleados_ocupados_[nEmpleado].liberar();
}

void JefeDeEstacion::setIdEmpleado(int nEmpleado, int idEmpleado) {
	indentificadores_empleados_[nEmpleado] = idEmpleado;
}

void JefeDeEstacion::addEmpleado(Empleado* empleado) {
	procesos_empleado.push_back(empleado);
}
